#!/usr/bin/env python
# -*- coding: UTF-8 -*-
'''
    感知器
'''
class Perception(object):
    def __init__(self, input_num, activator):
        '''
        初始化感知器
        :param input_num: 输入参数的个数
        :param activator:  激活函数
        '''
        self.activator = activator
        # 权重向量初始化为0
        self.weights = [0.0 for _ in range(input_num)]
        # 偏置向初始化为0
        self.bias = 0

    def __str__(self):
        '''
        打印学习到的权重及偏置
        :return:
        '''
        return 'weights\t:%s\nbias\t:%f\n' % (self.weights, self.bias)


    def predict(self, input_vec):
        '''
        输入向量，输出感知器的计算结果
        :param input_vec: 输入向量
        :return:  感知器的计算结果
        '''
        # 把 input_vec[x1,x2,x3....]和wights[w1,w2,w3....] 打包在一起，变成[(x1,w1),(x2,w2),(x3,w3),....]
        zipResult = zip(input_vec, self.weights)
        # 然后利用 map 函数计算 [x1*w1, x2*w2, x3*w3,...]
        mapResult = map(lambda (x, w): x * w, zipResult)
        # 最后利用 reduce 求和
        reduceResult = reduce(lambda a,b: a+b, mapResult, 0.0)+self.bias

        return self.activator(reduceResult)


    def train(self, input_vecs, labels, iteration, rate):
        '''
        输入训练数据
        :param input_vec: 一组向量
        :param labels:  与每一个向量对应的label
        :param iteration: 训练轮数
        :param rate: 学习率
        :return:
        '''
        for i in range(iteration):
            self._one_iteration(input_vecs, labels, rate)


    def _one_iteration(self, input_vecs, labels, rate):
        '''
        一次迭代，把所有的训练数据过一遍
        :return:
        '''
        # 把输入和输出打包在一起，成为样本的列表[(input_vec, label), ....],每一个训练样本是(input_vec, label)
        samples = zip(input_vecs, labels)
        # 对每一个样本，按照感知器规则更新权重
        for(input_vec, label) in samples:
            # 计算感知器在当前权重下的输出
            out_put = self.predict(input_vec)
            # 更新权重
            self._update_weight(input_vec, out_put, label, rate)


    def _update_weight(self, input_vec, output, label, rate):
        '''
        按照感知器的规则更新权重
        :param input_vec: 输入向量
        :param output:  对应输入的输出
        :param label:  当前输入向量对应的label
        :param rate: 学习率
        :return:
        '''
        # 把 input_vec[x1,x2,x3....]和wights[w1,w2,w3....] 打包在一起，变成[(x1,w1),(x2,w2),(x3,w3),....]
        # 利用感知器规则更新权重
        delta = label - output # 损失
        self.weights = map(lambda (x, w): w + rate * delta *x, zip(input_vec, self.weights))
        # 更新 偏置
        self.bias += rate * delta

